/* --------------------------------------------------------------------------
 * File:SpaceTimeNetwork.java - 公交排班模型3 - 仅排班、不排司机
 * --------------------------------------------------------------------------
 * 0-1变量x_b_i_j_t_s
 * x_b_i_j_t_s = 1,表示营运车辆 b从站点i发往站点j，t时刻出发，s时刻到达
 * --------------------------------------------------------------------------
 * 目标：Max 0
 * --------------------------------------------------------------------------
 */

import ilog.concert.IloException;
import ilog.concert.IloLinearNumExpr;
import ilog.concert.IloNumVar;
import ilog.cplex.IloCplex;

import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class SpaceTimeNetwork {
//    static int    N = 4; // 站点数量（0-虚拟起点；1-主站；2-副站；3-虚拟终点）
//    static int    B = 10; // 营运车辆总数量
////    static int[]  T = {360, 380, 375, 395}; // 6：00-6：20、 6：15-6：35
////    static int[]  T = {360, 392, 375, 407}; // 6：00-6：32、 6：15-6：47
////    static int[]  T = {360, 415, 375, 430}; // 6：00-6：55、 6：15-7：10
//    static int[]  T = {360, 447, 375, 462}; // 6：00-7：27、 6：15-7：42
//    static double Imin = 5.0; // 发车最小间隔
//    static double Imax = 10.0; // 发车最大间隔
//    static int[]  travelTime = {10, 12}; // 下行单程时间、上行单程时间
//    static int[]  stopTime = {5, 10}; // 最小停站时间、最大停站时间
//    static int    ET = T[3] + travelTime[1]; // End Time营运时间终止时间
//    static int[]  AT = {0, T[1] - T[0], T[2] - T[0], T[3] - T[0], ET - T[0]}; // 0：00-1：27、 0：15-1：42、 1：54
//    static String DateTime;
//    static FileOutputStream propFile = null;
//    static MultiOutputStream multi = null;

    static int    N = 4; // 站点数量（0-虚拟起点；1-主站；2-副站；3-虚拟终点）
    static int    B = 10; // 营运车辆总数量
    static int[]  T = {360, 540, 390, 570}; // 6：00-9：00、 6：30-9：30
//    static int[]  T = {360, 720, 390, 750}; // 6：00-12：00、 6：30-12：30
    static double Imin = 5.0; // 发车最小间隔
    static double Imax = 10.0; // 发车最大间隔
    static int[]  travelTime = {20, 22}; // 下行单程时间、上行单程时间
    static int[]  stopTime = {5, 10}; // 最小停站时间、最大停站时间
    static int    ET = T[3] + travelTime[1]; // End Time营运时间终止时间 9:42
    static int[]  AT = {0, T[1] - T[0], T[2] - T[0], T[3] - T[0], ET - T[0]}; // 0：00-3：00、 0：30-3：30、 3：42
    static String DateTime;
    static FileOutputStream propFile = null;
    static MultiOutputStream multi = null;

    public static void main(String[] args ) throws Exception {
        long start = System.currentTimeMillis();

        // 获取当前日期和时间
        LocalDateTime now = LocalDateTime.now();
        System.out.println(now);
        // 创建日期和时间格式化器，将不合法字符替换为下划线
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
        DateTime = now.format(formatter);

        // 设置可以输出到控制台，也可以输出到定向指定文件，也可以同时输出到控制台和定向指定文件
        propFile = new FileOutputStream("./Log_STN/STN_" + DateTime + ".txt");
        multi = new MultiOutputStream(new PrintStream(propFile),System.out);
        System.setOut(new PrintStream(multi)); // 设置同时输出到控制台和定向指定文件
//        System.setOut(new PrintStream(propFile)); // 设置只输出到定向指定文件
//        System.setOut(System.out); // 设置只输出到控制台

        Model();

        System.out.println(" ");
        long end = System.currentTimeMillis();
        System.out.println((end - start) + "毫秒");
        multi.close();
        propFile.close();
    }

    public static void Model(){
        try {
            IloCplex cplex = new IloCplex();
            int[]  stCount = new int[13]; // 记录约束条数
//            cplex.setOut(null);
//            cplex.setOut(new FileOutputStream("./cplex_log_" + DateTime + ".txt"));
//            cplex.setParam(IloCplex.DoubleParam.TimeLimit, 300);
//            cplex.setParam(IloCplex.Param.MIP.Tolerances.MIPGap, 2e-1); // 设置整数规划目标间隙

            IloNumVar[][][][][] x =  new IloNumVar[B][N][N][(AT[4] + 1)][AT[4] + 1];
            System.out.println("5个下标的变量数：" + B * N * N * (AT[4] + 1) * (AT[4] + 1));

            long mi1 = System.currentTimeMillis();
            int arr;
            int[] actualV = new int[7];
//            for (int b = 0; b < B; b++) {
//                for (int i = 0; i < N; i++) {
//                    for (int j = 1; j < N; j++) {
//                        for (int t = 0; t <= AT[4]; t++) {
//                            for (int s = 0; s <= AT[4]; s++) {
//                                x[b][i][j][t][s] = cplex.intVar(0, 1, "x_" + b + "_" + i + "_" + j + "_" + (t + T[0]) + "_" + (s + T[0]));// 创建0-1变量
//////                                cplex.addLe(x[b][i][j][t][s],1);
//                            }
//                        }
//                    }
//                }
//            }

            for (int b = 0; b < B; b++) {
                for (int s = 0; s <= AT[1]; s++) {
                    // 虚拟起点--->主站
                    x[b][0][1][AT[0]][s] = cplex.intVar(0, 1, "x_" + b + "_" + 0 + "_" + 1 + "_" + (AT[0] + T[0]) + "_" + (s + T[0]));// 创建0-1变量
                    actualV[0]++;
                }

                // 虚拟起点--->虚拟终点
                x[b][0][3][AT[0]][AT[4]] = cplex.intVar(0, 1, "x_" + b + "_" + 0 + "_" + 1 + "_" + (AT[0] + T[0]) + "_" + (AT[4] + T[0]));// 创建0-1变量
                actualV[1]++;

                for (int t = 0; t <= AT[4]; t++) {
                    if (t >= AT[2] && t <= AT[3]) {
                        // 主站--->虚拟终点
                        arr = t + travelTime[1];
                        x[b][1][3][arr][AT[4]] = cplex.intVar(0, 1, "x_" + b + "_" + 1 + "_" + 3 + "_" + (arr + T[0]) + "_" + (AT[4] + T[0]));// 创建0-1变量
                        actualV[2]++;

                        // 副站--->主站
                        x[b][2][1][t][arr] = cplex.intVar(0, 1, "x_" + b + "_" + 2 + "_" + 1 + "_" + (t + T[0]) + "_" + (arr + T[0]));// 创建0-1变量
                        actualV[3]++;
                    }

                    // 主站--->副站
                    if (t <= AT[1]) {
                        arr = t + travelTime[0];
                        x[b][1][2][t][arr] = cplex.intVar(0, 1, "x_" + b + "_" + 1 + "_" + 2 + "_" + (t + T[0]) + "_" + (arr + T[0]));// 创建0-1变量
                        actualV[4]++;
                    }

                    // 主站--->主站（停站）
                    if (t + stopTime[0] <= AT[1]) {
                        for (int tTemp = t + stopTime[0]; tTemp <= t + stopTime[1] && tTemp <= AT[1]; tTemp++) {
                            x[b][1][1][t][tTemp] = cplex.intVar(0, 1, "x_" + b + "_" + 1 + "_" + 1 + "_" + (t + T[0]) + "_" + (tTemp + T[0]));// 创建0-1变量
                            actualV[5]++;
                        }
                    }

                    // 副站--->副站（停站）
                    if (t >= travelTime[0] && t + stopTime[0] <= AT[3]) {
                        for (int tTemp = t + stopTime[0]; tTemp <= t + stopTime[1] && tTemp <= AT[3]; tTemp++) {
                            x[b][2][2][t][tTemp] = cplex.intVar(0, 1, "x_" + b + "_" + 2 + "_" + 2 + "_" + (t + T[0]) + "_" + (tTemp + T[0]));// 创建0-1变量
                            actualV[6]++;
                        }
                    }
                }
            }


            long mi2 = System.currentTimeMillis();
            System.out.println((mi2 - mi1) + "毫秒");

            int allActualV = 0;
            for (int i = 0; i < actualV.length; i++) {
                allActualV += actualV[i];
//                System.out.println("actualVariable" + (i + 1) + ": " + actualV[i] + "个");
            }
            System.out.println("allActualVariable: " + allActualV + "个");


//             Objective Function: Maximize Cost
//            IloLinearNumExpr objExpr = cplex.linearNumExpr();
//            for (int b = 0; b < B; b++) {
//                objExpr.addTerm(1, x[b][0][K + 1]);
//            }
//            cplex.addMaximize(objExpr);
//            System.out.println("Maximize x_b_0_K+1 即最小化使用车辆数");

//            cplex.addMinimize(objExpr);
//            System.out.println("Minimize x_b_0_K+1 即最大化使用车辆数");

            cplex.addMaximize(String.valueOf(0));
            System.out.println("Max 0");


            // Constraint1:车次虚拟起点约束
            for (int b = 0; b < B; b++) {
                IloLinearNumExpr Expr1  = cplex.linearNumExpr();
                for (int s = 0; s <= AT[1]; s++) {
                    Expr1.addTerm(1, x[b][0][1][0][s]);
                }
                Expr1.addTerm(1, x[b][0][3][0][AT[4]]);
                cplex.addEq(Expr1, 1, "OriginConstraint" + stCount[0]);
                stCount[0]++;
            }

            // Constraint2 - 4:车次中间约束
            // 对于主站而言，出入度平衡1(虚拟起点--->主站--->副站)
            for (int b = 0; b < B; b++) {
                for (int s = 0; s <= AT[1]; s++) {
                    IloLinearNumExpr Expr2  = cplex.linearNumExpr();
                    arr = s + travelTime[0];
                    Expr2.addTerm(1, x[b][0][1][0][s]);
                    if (s - stopTime[0] >= 0) {
                        for (int tTemp = s - stopTime[0]; tTemp >= s - stopTime[1] && tTemp >= 0; tTemp--) {
                            Expr2.addTerm(1, x[b][1][1][tTemp][s]);
                        }
                    }
                    cplex.addEq(Expr2, x[b][1][2][s][arr], "MainStationBalance1_" + stCount[1]);
                    stCount[1]++;
                }
            }

            // 对于主站而言，出入度平衡2(副站--->主站--->副站 or 副站--->主站--->虚拟终点)
            for (int b = 0; b < B; b++) {
                for (int t = AT[2]; t <= AT[3]; t++) {
                    IloLinearNumExpr Expr3  = cplex.linearNumExpr();
                    arr = t + travelTime[1];
//                    Expr3.addTerm(1, x[b][1][3][arr][arr]);
                    Expr3.addTerm(1, x[b][1][3][arr][AT[4]]);

                    for (int stTemp = stopTime[0]; stTemp <= stopTime[1] && (arr + stTemp <= AT[1]); stTemp++) {
                        Expr3.addTerm(1, x[b][1][1][arr][arr + stTemp]);
                        IloLinearNumExpr Expr32  = cplex.linearNumExpr();
                        for (int tTemp = arr + stTemp - stopTime[1]; tTemp <= arr + stTemp - stopTime[0]; tTemp++) {
                            Expr32.addTerm(1, x[b][1][1][tTemp][arr + stTemp]);
                        }
                        cplex.addEq(Expr32, x[b][1][2][arr + stTemp][arr + stTemp + travelTime[0]], "MainStationBalance2_2_" + stCount[2]);
                        stCount[2]++;
                    }
                    cplex.addEq(Expr3, x[b][2][1][t][arr], "MainStationBalance2_1_" + stCount[2]);
                    stCount[2]++;
                }
            }


            // 对于副站而言，出入度平衡(主站--->副站--->主站)
            for (int b = 0; b < B; b++) {
                for (int t = 0; t <= AT[1]; t++) {
                    IloLinearNumExpr Expr4  = cplex.linearNumExpr();
                    arr = t + travelTime[0];

                    for (int stTemp = stopTime[0]; stTemp <= stopTime[1] && (arr + stTemp <= AT[3]); stTemp++) {
                        Expr4.addTerm(1, x[b][2][2][arr][arr + stTemp]);
                        if (arr + stTemp - stopTime[1] >= travelTime[0]) {
                            IloLinearNumExpr Expr42  = cplex.linearNumExpr();
                            for (int tTemp = arr + stTemp - stopTime[1]; tTemp <= arr + stTemp - stopTime[0]; tTemp++) {
                                Expr42.addTerm(1, x[b][2][2][tTemp][arr + stTemp]);
                            }
                            cplex.addEq(Expr42, x[b][2][1][arr + stTemp][arr + stTemp + travelTime[1]], "SubstaionBalance2_" + stCount[3]);
                            stCount[3]++;
                        }
                    }
                    cplex.addEq(Expr4, x[b][1][2][t][arr], "SubstaionBalance1_" + stCount[3]);
                    stCount[3]++;
                }
            }


            // Constraints5: 车次虚拟终点约束
            for (int b = 0; b < B; b++) {
                IloLinearNumExpr Expr5  = cplex.linearNumExpr();
                for (int t = AT[2]; t <= AT[3]; t++) {
                    arr = t + travelTime[1];
                    Expr5.addTerm(1, x[b][1][3][arr][AT[4]]);
                }
                Expr5.addTerm(1, x[b][0][3][0][AT[4]]);
                cplex.addEq(Expr5, 1, "DestinationConstraint" + stCount[4]);
                stCount[4]++;
            }

            // Constraint6-7: 主站-->副站 发车间隔约束
            for (int t = 0; t <= AT[1]; t++) {
                for (int b = 0; b < B; b++) {
                    if (t + Imin <= AT[1]) {
                        IloLinearNumExpr Expr6  = cplex.linearNumExpr();
                        for (int bTemp = 0; bTemp < B; bTemp++) {
                            for (int tTemp = t + 1; tTemp <= t + Imax && tTemp <= AT[1]; tTemp++) {
                                arr = tTemp + travelTime[0];
                                Expr6.addTerm(1, x[bTemp][1][2][tTemp][arr]);
                            }
                        }
                        arr = t + travelTime[0];
                        cplex.addGe(Expr6, x[b][1][2][t][arr], "IntervalConstraint1_gt_" + stCount[5]);
                        stCount[5]++;
                    }

                    if (t + 1 <= AT[1]) {
                        IloLinearNumExpr Expr7  = cplex.linearNumExpr();
                        for (int bTemp = 0; bTemp < B; bTemp++) {
                            for (int tTemp = t + 1; tTemp <= t + Imin - 1 && tTemp <= AT[1]; tTemp++) {
                                arr = tTemp + travelTime[0];
                                Expr7.addTerm(1, x[bTemp][1][2][tTemp][arr]);
                            }
                        }
                        arr = t + travelTime[0];
                        cplex.addLe(cplex.sum(Expr7, cplex.prod(1, x[b][1][2][t][arr])), 1, "IntervalConstraint1_lt_" + stCount[6]);
                        stCount[6]++;
                    }
                }
            }


            // Constraint8-9: 副站-->主站 发车间隔约束
            for (int t = AT[2]; t <= AT[3]; t++) {
                for (int b = 0; b < B; b++) {
                    if (t + Imin <= AT[3]) {
                        IloLinearNumExpr Expr8  = cplex.linearNumExpr();
                        for (int bTemp = 0; bTemp < B; bTemp++) {
                            for (int tTemp = t + 1; tTemp <= t + Imax && tTemp <= AT[3]; tTemp++) {
                                arr = tTemp + travelTime[1];
                                Expr8.addTerm(1, x[bTemp][2][1][tTemp][arr]);
                            }
                        }
                        arr = t + travelTime[1];
                        cplex.addGe(Expr8, x[b][2][1][t][arr], "IntervalConstraint2_gt_" + stCount[7]);
                        stCount[7]++;
                    }

                    if (t + 1 <= AT[3]) {
                        IloLinearNumExpr Expr9  = cplex.linearNumExpr();
                        for (int bTemp = 0; bTemp < B; bTemp++) {
                            for (int tTemp = t + 1; tTemp <= t + Imin - 1 && tTemp <= AT[3]; tTemp++) {
                                arr = tTemp + travelTime[1];
                                Expr9.addTerm(1, x[bTemp][2][1][tTemp][arr]);
                            }
                        }
                        arr = t + travelTime[1];
                        cplex.addLe(cplex.sum(Expr9, cplex.prod(1, x[b][2][1][t][arr])), 1, "IntervalConstraint2_lt_" + stCount[8]);
                        stCount[8]++;
                    }
                }
            }

            // Constraints10-13: 主副站首末班发车时间约束
            // 主站副站首班发车时间固定(虚拟起点--->主站--->副站--->主站)
            IloLinearNumExpr Expr10  = cplex.linearNumExpr();
            for (int b = 0; b < B; b++) {
                Expr10.addTerm(1, x[b][0][1][0][0]);
                arr = AT[2] + travelTime[1];
                cplex.addEq(cplex.sum(cplex.prod(2, x[b][0][1][0][0]),
                        cplex.prod(-1, x[b][1][2][0][travelTime[0]]),
                        cplex.prod(-1, x[b][2][1][AT[2]][arr])), 0, "SubtationFirstDepart" + stCount[9]);
                stCount[9]++;
            }
            cplex.addEq(Expr10, 1, "MainStationFirstDepart" + stCount[10]);
            stCount[10]++;

            // 主站副站末班发车时间固定(主站--->副站--->主站--->虚拟终点)
            IloLinearNumExpr Expr11  = cplex.linearNumExpr();
            for (int b = 0; b < B; b++) {
                arr = AT[1] + travelTime[0];
                Expr11.addTerm(1, x[b][1][2][AT[1]][arr]);
                cplex.addEq(cplex.sum(cplex.prod(2, x[b][1][2][AT[1]][arr]),
                        cplex.prod(-1, x[b][2][1][AT[3]][AT[4]]),
                        cplex.prod(-1, x[b][1][3][AT[4]][AT[4]])), 0, "SubtationLastDepart" + stCount[11]);
                stCount[11]++;
            }
            cplex.addEq(Expr11, 1, "MainStationLastDepart" + stCount[12]);
            stCount[12]++;

            int allConstraints = 0;
            for (int i = 0; i < stCount.length; i++) {
                allConstraints += stCount[i];
                System.out.println("Constraints" + (i + 1) + ": " + stCount[i] + "条");
            }
            System.out.println("allConstraints: " + allConstraints + "条");

            cplex.exportModel("./Lp_STN/STN_" + DateTime +".lp");
            if (cplex.solve()) {
                System.out.println("Solution status: " + cplex.getStatus());

//                System.out.println("Variables:");
                double[][][][][] xVals = new double[B][N][N][(AT[4] + 1)][AT[4] + 1];
                for (int b = 0; b < B; b++) {
                    for (int i = 0; i < N; i++) {
                        for (int j = 1; j < N; j++) {
                            for (int t = 0; t <= AT[4]; t++) {
                                for (int s = 0; s <= AT[4]; s++) {
//                                    System.out.println(x[b][i][j][t][s]);
                                    // 虚拟起点--->主站
                                    if (i == 0 && j == 1 && t == AT[0] && s <= AT[1]) {
                                        xVals[b][i][j][t][s] = cplex.getValue(x[b][i][j][t][s]);
                                    }
                                    // 虚拟起点--->虚拟终点
                                    if (i == 0 && j == 3 && t == AT[0] && s == AT[4]) {
                                        xVals[b][i][j][t][s] = cplex.getValue(x[b][i][j][t][s]);
                                    }
                                    // 主站--->虚拟终点
                                    if (i == 1 && j == 3 && t >= AT[2] + travelTime[1] && s == AT[4]) {
                                        xVals[b][i][j][t][s] = cplex.getValue(x[b][i][j][t][s]);
                                    }
                                    // 主站--->副站
                                    if (i == 1 && j == 2 && t <= AT[1] && s == t + travelTime[0]) {
                                        xVals[b][i][j][t][s] = cplex.getValue(x[b][i][j][t][s]);
                                    }
                                    // 副站--->主站
                                    if (i == 2 && j == 1 && t >= AT[2] && t <= AT[3] && s == t + travelTime[1]) {
                                        xVals[b][i][j][t][s] = cplex.getValue(x[b][i][j][t][s]);
                                    }
                                    for (int stopTimeTemp = stopTime[0]; stopTimeTemp <= stopTime[1]; stopTimeTemp++) {
                                        // 主站--->主站（停站）
                                        if (i == 1 && j == 1 && t >= AT[2] + travelTime[1] && t <= AT[3] + travelTime[1] && s == t + stopTimeTemp && s <= AT[1]) {
                                            xVals[b][i][j][t][s] = cplex.getValue(x[b][i][j][t][s]);
                                        }
                                        // 副站--->副站（停站）
                                        if (i == 2 && j == 2 && t >= AT[0] + travelTime[0] && t <= AT[1] + travelTime[0] && s == t + stopTimeTemp && s <= AT[3]) {
                                            xVals[b][i][j][t][s] = cplex.getValue(x[b][i][j][t][s]);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                BufferedWriter writer = new BufferedWriter(new FileWriter("./output" + DateTime + ".txt"));
                int unusedVehiclesNumber = 0;
                for (int i = 0; i < N; i++) {
                    for (int j = 1; j < N; j++) {
                        for (int t = 0; t <= AT[4]; t++) {
                            for (int s = 0; s <= AT[4]; s++) {
                                for (int b = 0; b < B; b++) {
                                    if (Math.abs(xVals[b][i][j][t][s] - 1) <= 1e-6) {
                                        System.out.println("x_" + b + "_" + i + "_" + j + "_" + (t + T[0]) + "_" + (s + T[0]) + " = " + xVals[b][i][j][t][s]);
                                        writer.write("x_" + b + "_" + i + "_" + j + "_" + + (t + T[0]) + "_" + (s + T[0]) + " = " + xVals[b][i][j][t][s]);
                                        writer.write("\n"); // 在每个元素之后添加换行符
                                        if (i == 0 && j == 3) {
                                            unusedVehiclesNumber++;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                writer.close();
                System.out.println("结果已成功写入到output.txt文件中。");
                // Print results
                System.out.println("Obj:" + cplex.getObjValue() + "  已使用车辆数: " + (B - unusedVehiclesNumber) + " 辆");
            } else {
                System.err.println("Solution status: " + cplex.getStatus()); // 红色输出错误消息
//                System.out.println("Solution status: " + cplex.getStatus());
            }
            System.out.println("------------------------------------------------------------------------------");
            System.out.flush(); // 手动刷新输出缓冲区
            cplex.end();
        }
        catch (IloException | IOException e) {
            e.printStackTrace();
        }
    }
}




